using System;
using System.Collections.Generic;
using NyMPH.Persistence;
namespace NyMPH.Patches
{
    /// <summary>
    /// Represents a collection of metadata about a single Patch.
    /// </summary>
    public class Patch : IPersistence, ICloneable, IComparable<Patch>, IEquatable<Patch>
    {
        private int _OID;
        private string _name;
        private string _originalLocation;
        private int _usage;
        private DateTime _lastModified;
        private DateTime _whenCreated;
        private byte _rating;
        private string _description;
        private PatchData _data;
        private bool _isNew;
        private bool _isArchived;
        private List<Keyword> _keywords;
        private List<PatchData> _versions;

        #region Properties
        /// <summary>
        /// The unique ObjectID within the NyMPH Patch table.
        /// </summary>
        public int OID
        {
            set
            {
                _OID = value;
                this._hasChanges = true;
            }
            get { return _OID; }
        }

        /// <summary>
        /// The Name of the Patch. This will be truncated to 16 characters.
        /// </summary>
        public string Name
        {
            set
            {
                //truncate to 16 chars
                _name = (value.Length > 16 ? value.Remove(16) : value);
                this._hasChanges = true;
            }
            get { return _name; }
        }

        /// <summary>
        /// The original filesystem path the Patch was imported from.
        /// </summary>
        public string OriginalLocation
        {
            set
            {
                _originalLocation = value;
                this._hasChanges = true;
            }
            get { return _originalLocation; }
        }

        /// <summary>
        /// The number of times the Patch has been loaded into the Editor Software.
        /// Call the <c>IncrementUsage()</c> method to increase by 1, or explicitly set this property.
        /// </summary>
        public int Usage
        {
            set
            {
                _usage = value;
                this._hasChanges = true;
            }
            get { return _usage; }
        }

        /// <summary>
        /// The date and time the Patch data was last modified and saved.
        /// </summary>
        public DateTime LastModified
        {
            set
            {
                _lastModified = value;
                this._hasChanges = true;
            }
            get { return _lastModified; }
        }

        /// <summary>
        /// The date and time this Patch was added to the NyMPH System.
        /// </summary>
        public DateTime WhenCreated
        {
            set
            {
                _whenCreated = value;
                this._hasChanges = true;
            }
            get { return _whenCreated; }
        }

        /// <summary>
        /// A user rating of the Patch. Valid ranges are 1 - 5, with 5 being the highest rating. A value of 0 means a rating has not been set.
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">This exception is thrown if the Rating property is attempted to be set to an invalid value.</exception>            
        public byte Rating
        {
            set
            {
                //only assign if rating is between 1 and five inclusive.
                if ((value >= 0) && (value <= 5))
                {
                    _rating = value;
                }
                else
                {
                    throw new ArgumentOutOfRangeException("Rating", value,
                        "The Rating value must be between 1 and 5 inclusive, else 0 for undefined.");
                }
            }
            get { return _rating; }
        }

        /// <summary>
        /// A description of this Patch. The corresponding field in the Database is limited to 200 chars, so this limit is enforced here.
        /// </summary>
        public string Description
        {
            set
            {
                //truncate description to 200 chars if needed.
                _description = (value.Length > 200 ? value.Remove(200) : value);
                this._hasChanges = true;
            }
            get { return _description; }
        }

        /// <summary>
        /// A reference to the active PatchData object this Patch uses.
        /// </summary>
        public PatchData Data
        {
            //TODO: Use this to set Active Version?
            set
            {
                _data = value;
                this._hasChanges = true;
            }
            get { return _data; }
        }

        /// <summary>
        /// A flag that indicates this Patch object is new and has not had it's Metadata edited yet.
        /// </summary>
        public bool IsNew
        {
            set
            {
                _isNew = value;
                this._hasChanges = true;
            }
            get { return _isNew; }
        }

        /// <summary>
        /// This indicates the Patch is in an 'Archived' state and will not been shown in regular Patch lists.
        /// </summary>
        public bool IsArchived
        {
            set
            {
                _isArchived = value;
                this._hasChanges = true;
            }
            get { return _isArchived; }
        }

        /// <summary>
        /// The list of Keyword objects associated with this patch.
        /// </summary>
        public List<Keyword> Keywords
        {
            //TODO: add wrapper
            set { }
            get { throw new NotImplementedException("TODO:"); }
        }

        /// <summary>
        /// The list of PatchData OID's available for this Patch.
        /// </summary>
        public List<string> Versions
        {
            //TODO: add wrapper
            set { }
            get { throw new NotImplementedException("TODO:"); }
        }



        #endregion

        #region Constructors

        /// <summary>
        /// Default constructor
        /// </summary>
        public Patch()
        {
            this.Keywords = new List<Keyword>();
            //this.Versions = new List<PatchData>();
        }

        #endregion

        #region Methods
        public PatchData GetPatchData()
        {
            // TODO add implementation and return statement
            throw new NotImplementedException("TODO:");
        }

        /// <summary>
        /// Sets the Data property to an instance of PatchData that should be considered the 'active' version.
        /// </summary>
        /// <param name="Version">An instance of PatchData</param>
        public void SetActiveVersion(PatchData Version)
        {           
            if (Version != null)
            {
                _data = Version;
              // SetModified();
            }
        }

        /// <summary>
        /// Increment the Usage propety by 1.
        /// </summary>
        public void IncrementUsage()
        {
            this.Usage = this.Usage++;
        }

        /// <summary>
        /// Create a memberwise copy of this Patch. The OID will be null.
        /// </summary>
        /// <returns>A new instance of Patch with the same attributes. The Keywords and Versions lists will be filled from the original lists, as is the Patch Data.</returns>
        public void Clone(ref Patch NewPatch)
        {
            NewPatch.Name = this.Name;
            NewPatch.OriginalLocation = this.OriginalLocation;
            NewPatch.IsArchived = this.IsArchived;
            NewPatch.IsNew = this.IsNew;
            NewPatch.LastModified = this.LastModified;
            NewPatch.WhenCreated = this.WhenCreated;
            NewPatch.Rating = this.Rating;
            NewPatch.Usage = this.Usage;
            NewPatch.Description = this.Description;

            //TODO: Add Cloning support to PatchData
            NewPatch.Data = this.Data;

            //Add original keyword list as new list
            NewPatch.Keywords.AddRange(this.Keywords);

            //Add original list of Versions to the 
            NewPatch.Versions.AddRange( this.Versions);

        }

        #endregion

        #region ICloneable Members

        object ICloneable.Clone()
        {
//            return this.Clone(ref new Patch());
            throw new NotImplementedException("TODO:");
        }

        #endregion

        #region IPersistence Members

        private bool _hasChanges = false;

        bool IPersistence.Save()
        {
            throw new NotImplementedException();
        }

        bool IPersistence.Load()
        {
            throw new NotImplementedException();
        }

        bool IPersistence.Delete()
        {
            throw new NotImplementedException();
        }

        int IPersistence.GetOID()
        {
            throw new NotImplementedException();
        }

        void IPersistence.Find(Criteria item)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Indicates whether this object has unsaved changes.
        /// </summary>
        /// <returns>True if changes need to be saved, else False.</returns>
        bool IPersistence.HasChanges()
        {
            return _hasChanges;
        }

        /// <summary>
        /// Set a flag indicating this object has changed from its persisted state in the database.
        /// </summary>
        /// <param name="Dirty">True if the object has changes, else False.</param>
        void IPersistence.HasChanges(bool Changed)
        {
            _hasChanges = Changed;
        }

        #endregion


        #region IEquatable<Patch> Members
        /// <summary>
        /// Determines whether this instance of Patch and a specified object, which must also be a Patch object, have the same values.
        /// </summary>
        /// <param name="Obj">The Patch object to compare values with</param>
        /// <returns>True if all properties have matching values, otherwise false.</returns>
        /// <remarks></remarks>
        public bool Equals(Patch Obj)
        {
            //AND all equality evaluations together
            //Todo: Is it necessary to compare Keyword and PatchData lists? If so, perhaps make a custom collection class?

            //compare 'easy' ones first to conserve resources; if any of the properties are different it is not equal and we can skip the more costly evaluations  
            if 
                ((this.Name == Obj.Name && this.IsArchived == Obj.IsArchived && this.IsNew == Obj.IsNew && this.Rating == Obj.Rating && this.Usage == Obj.Usage)
                &&
                (this.OriginalLocation == Obj.OriginalLocation && this.LastModified == Obj.LastModified && this.WhenCreated == Obj.WhenCreated && this.Description == Obj.Description))
            {
                return this.Data == Obj.Data;
                //TODO: compare List objects if needed
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Determines whether this instance of Patch and a specified object, which must also be a Patch object, have the same values.
        /// </summary>
        /// <param name="Obj">The Patch to compare values with</param>
        /// <returns>True if all properties have matching values, otherwise false.</returns>
        /// <remarks>Overrides the Object.Equals(object) method. This ensures all invocations of Equals() for the Patch type return the same result.</remarks>
        public override bool Equals(object Obj)
        {
            //use base class method for comparing nulls; casting them would throw an exception
            if (Obj == null) return base.Equals(Obj);

            //make sure object can be cast to Patch and call the Equals(Patch) method
            if (Obj is Patch)
            {
                return Equals((Patch)Obj);
            }
            else
            {
                throw new InvalidCastException("The 'Obj' argument is not a Patch object.");
            }
        }

        /// <summary>
        /// Provides a hash value based on XORing the Name, Description, LastModified, and WhenAdded properties together. Two Patch objects with the same values will produce the same hash code, although it isn't guaranteed to be unique
        /// </summary>
        /// <returns>The hash created from (Name xor Description xor LastModified xor WhenAdded)</returns>
        /// <remarks>MSDN recommends overriding this method when implementing IEquatable. </remarks>
        public override int GetHashCode()
        {
            return this.Name.GetHashCode() ^ this.Description.GetHashCode() ^ this.LastModified.GetHashCode() ^ this.WhenCreated.GetHashCode();
        }

        /// <summary>
        /// Create equality operator for Patch objects; allows expressions like {if (PatchA = PatchB)} to compare each member value instead of the object reference
        /// </summary>
        public static bool operator ==(Patch PatchA, Patch PatchB)
        {
            return PatchA.Equals(PatchB);
        }

        /// <summary>
        /// Create inequality operator for Patch objects; allows expressions like {if (PatchA != PatchB)} to compare each member value instead of the object reference
        /// </summary>
        public static bool operator !=(Patch PatchA, Patch PatchB)
        {
            return !PatchA.Equals(PatchB);
        }

        #endregion

        #region IComparable<Patch> Members

        /// <summary>
        /// This is required to implement IComparable, which is used when sorting Patchs. These are sorted by Name 
        /// </summary>
        /// <param name="other">The Patch object to compare values with</param>
        /// <returns>Less than 0 if 'other' Patch should be earlier in the sort order, 0 if equal, and greater than zero if the 'other' Patch should come after this Patch.</returns>
        /// <remarks></remarks>
        public int CompareTo(Patch other)
        {
            //compare by name 
            return this.Name.CompareTo(other.Name);
        }

        #endregion
    }
}
